import java.util.ArrayList;
import java.util.Arrays;
/**
 * The BigTwo class implements the CardGame interface and is used to model a Big Two card game. 
 * @author Xu Chen
 */
public class BigTwo implements CardGame {
	/**an integer specifying the number of players
	*/
	private int numOfPlayers=0;
	/**a deck of cards
	*/
	private Deck deck;
	
	/**a list of players
	*/
	private ArrayList<CardGamePlayer> playerList;
	
	/**a list of hands played on the table
	*/
	private ArrayList<Hand> handsOnTable=new ArrayList<Hand>() ;
	
	/**an integer specifying the index of the current player
	*/
	private int currentPlayerIdx; 
	
	/**a BigTwoUI object for providing the user interface
	*/
	private BigTwoUI ui;
	
	
	/**
	 * a constructor for creating a Big Two card game.
	*/
	public BigTwo() {
		this.playerList=new ArrayList<CardGamePlayer>();
		CardGamePlayer p1 = new CardGamePlayer();
		CardGamePlayer p2 = new CardGamePlayer();
		CardGamePlayer p3 = new CardGamePlayer();
		CardGamePlayer p4 = new CardGamePlayer();
		this.playerList.add(p1);
		this.playerList.add(p2);
		this.playerList.add(p3);
		this.playerList.add(p4);
		this.numOfPlayers=this.playerList.size();
		this.ui = new BigTwoUI(this);
	}
	
	/**
	*a method for retrieving the number of players.
	*@return this.numOfPlayers the number of players.
	*/
	public int getNumOfPlayers() {
		return this.numOfPlayers;
	}
	
	/**
	*a method for retrieving the deck of cards in a game.
	*@return this.deck the deck of cards.
	*/
	public Deck getDeck() {
		return this.deck;
	}
	
	/**
	*a method for retrieving an ArrayList containing all players.
	*@return this.playerList is an ArrayList containing all players.
	*/
	public ArrayList<CardGamePlayer> getPlayerList(){
		return this.playerList;
	}
	
	/**
	*a method for retrieving hands on table.
	*@return this.handsOnTable as all hands that have been put on table.
	*/
	public ArrayList<Hand> getHandsOnTable(){
		return this.handsOnTable;
	}
	
	/**
	*a method for retrieving Current Player Index as an integer.
	*@return this.currentPlayerIdx as index of current player
	*/
	public int getCurrentPlayerIdx() {
		return this.currentPlayerIdx;
	}
	
	/**
	*a method for starting/restarting the game with a given shuffled deck of cards.
	*@param deck is the deck of cards used to play the game
	*/
	public void start(Deck deck) {
		
		//clear players' hands
		for (int i=0;i<this.playerList.size();i++) {
			this.playerList.get(i).removeAllCards();
		}
		
		//clear table
		this.handsOnTable=new ArrayList<Hand>();
		
		//reset current player index
		this.currentPlayerIdx=0;
		
		//initialize the deck
		deck.initialize();
		
		//shuffle deck
		deck.shuffle();
		
		//distributing the cards to the four players
		int a = 0;
		int hold_3_diam_idx=0;
		Card beginCard = new Card(0,2);
		for(int i = 0; i <52; i++){
			//Identify the player with Three of Diamonds
			if (deck.getCard(i).rank==beginCard.rank&&deck.getCard(i).suit==beginCard.suit) {
				hold_3_diam_idx=a%4;
			}
			this.playerList.get(a%4).addCard(deck.getCard(i));
			a++;
		}
		
		//make the first current player as the player who has diamond 3.
		this.currentPlayerIdx = hold_3_diam_idx;
		this.ui.setActivePlayer(hold_3_diam_idx);
		
		//Sort players' hands for better display
		playerList.get(0).getCardsInHand().sort();
		playerList.get(1).getCardsInHand().sort();
		playerList.get(2).getCardsInHand().sort();
		playerList.get(3).getCardsInHand().sort();

		//Resetting the UI (at the start of game)
		this.ui = new BigTwoUI(this);
		
		//Set an active player (at the start of game)
		this.ui.setActivePlayer(this.currentPlayerIdx);
		
		//Displaying table and messages (at the start of game)
		this.ui.repaint();
		
		//Wait for input (at the start of game)
		this.ui.promptActivePlayer();
		
		//while the game is not ended (while every player still has cards in their hand)
		while (this.endOfGame()==false) {
			
			//Resetting the UI, 
			this.ui = new BigTwoUI(this);
			
			//set an active player
			this.ui.setActivePlayer(this.currentPlayerIdx);
			
			//wait for input
			this.ui.promptActivePlayer();
			
			/**
			 * The displaying table during the game is written in checkMove
			 * Because if the player is making a valid move, the table should be displayed;
			 * if the player is making an invalid move, the table should not be displayed and should wait for the next input.
			 * */
			//this.ui.repaint();
		}
		
		//At the end of the game
		System.out.println("Game ends");
		for(int i = 0;i<4;i++) {
			//if this player wins
			if(this.playerList.get(i).getCardsInHand().isEmpty()) {
				System.out.println(this.playerList.get(i).getName()+" wins the game.");
			}
			//if this player lose
			else {
				System.out.println(this.playerList.get(i).getName()+" has "+this.playerList.get(i).getNumOfCards()+" cards in hand.");
			}
		}
	}
	
	/**
	*a method for a method for making a move by a player with the specified index using the cards specified by the list of indices.
	*@param playerIdx is the player index that is playing his hand now
	*@param cardIdx is the input of card index
	*/
	public void makeMove(int playerIdx,int[] cardIdx) {
		this.checkMove(playerIdx, cardIdx);
	}
	
	/**
	*a method for checking a move made by a player. 
	*@param playerIdx is the player index that is playing his hand now
	*@param cardIdx is the input of card index
	*/
	public void checkMove(int playerIdx,int[] cardIdx) {
		
		//set the diamond 3 card
		Card diamond_3 = new Card(0,2);

		//store the corresponding cards according to cardIdx
		CardList cardlist = new CardList();
		
		//whether to this.ui.repaint(); or not
		//this.ui.repaint(); when temp_valid==true
		boolean temp_valid = true;
		
		//if input is a list of int, cardIdx is not null
		if(cardIdx!=null) {
			//get the cards according to cardIdx
			cardlist=this.playerList.get(this.currentPlayerIdx).play(cardIdx);
			//if this cardIdx can get cards in the hand of this player (the index is not out of range)
			if(composeHand(this.playerList.get(this.currentPlayerIdx), cardlist)!=null) {
				//if this is the first hand of the whole game
				if(this.handsOnTable.isEmpty()) {
					//if the first player's first move contains diamond 3 and it is a valid move
					if(composeHand(this.playerList.get(this.currentPlayerIdx), cardlist).contains(diamond_3)&& 
						composeHand(playerList.get(this.currentPlayerIdx), cardlist).isValid()==true){
						//add this hand of cards to the handsOnTable
						this.handsOnTable.add(composeHand(this.playerList.get(playerIdx), cardlist));
						
						//Remove the cards that is played by the current player in this move
						Arrays.sort(cardIdx);
						for(int v=0;v<cardIdx.length;v++) {
							this.playerList.get(playerIdx).getCardsInHand().removeCard(cardIdx[v]);
							for(int j=0;j<cardIdx.length;j++) {
								cardIdx[j]--;
							}
						}
						
						//turn to the next player
						this.currentPlayerIdx++;
						this.currentPlayerIdx=this.currentPlayerIdx%4;
						
						//show table
						temp_valid=true;
					}
					//if the first player's first move doesn't contain diamond 3 or it is a invalid move
					else {
						System.out.println("Not a legal move!!!");
						//do not show table
						temp_valid=false;
					}
				}
				//if this is not the first hand of the whole game(which means there are already some cards on the table)
				else {
					//if the current player is the last player who plays the hands on table (3 Adjacent Passes)
					if(this.handsOnTable.get(this.handsOnTable.size()-1).getPlayer()==this.playerList.get(currentPlayerIdx)) {
						//add directly to the table
						this.handsOnTable.add(composeHand(this.playerList.get(playerIdx), cardlist));
						Arrays.sort(cardIdx);
						for(int v=0;v<cardIdx.length;v++) {
							this.playerList.get(playerIdx).getCardsInHand().removeCard(cardIdx[v]);
							for(int j=0;j<cardIdx.length;j++) {
								cardIdx[j]--;
							}
						}
						this.currentPlayerIdx++;
						this.currentPlayerIdx=this.currentPlayerIdx%4;

					}else{
						temp_valid=composeHand(playerList.get(this.currentPlayerIdx), cardlist).beats(this.handsOnTable.get(this.handsOnTable.size()-1));
						if(temp_valid==true) {
							this.handsOnTable.add(composeHand(this.playerList.get(playerIdx), cardlist));
							System.out.println("this.handsOnTable"+this.handsOnTable);
							
							Arrays.sort(cardIdx);
							for(int v=0;v<cardIdx.length;v++) {
								this.playerList.get(playerIdx).getCardsInHand().removeCard(cardIdx[v]);
								for(int j=0;j<cardIdx.length;j++) {
									cardIdx[j]--;
								}
							}
							this.currentPlayerIdx++;
							this.currentPlayerIdx=this.currentPlayerIdx%4;
							
						}else {
							System.out.println("Not a legal move!!!");
							temp_valid = false;
						}
					}
				}
			}
			//if this cardIdx can not get cards in the hand of this player (the index is out of range)
			else {
				System.out.println("Not a legal move!!!");
				temp_valid=false;
			}
		}
		//if input is some strings or nothing, not a list of integer
		else {
			//if table is empty
			if(this.handsOnTable.isEmpty()) {
				System.out.println("Not a legal move!!!");
				temp_valid = false;
			}
			//if table is not empty and this player is the last player who plays the hands on table (3 Adjacent Passes), he cannot pass
			else if(this.handsOnTable.get(this.handsOnTable.size()-1).getPlayer()==this.playerList.get(currentPlayerIdx)) {
				System.out.println("Not a legal move!!!");
				temp_valid = false;
			}
			//if this player is not the last player who plays the hands on table (3 Adjacent Passes), he can pass
			else {
			this.currentPlayerIdx++;
			this.currentPlayerIdx=this.currentPlayerIdx%4;
			this.ui.setActivePlayer(this.currentPlayerIdx);
			System.out.println("{Pass}"); 
			System.out.println("");
			temp_valid = true;
			}
		}
		
		//if temp_valid==true,  show the table.
		if(temp_valid==true) {
			this.ui.setActivePlayer(this.currentPlayerIdx);
			this.ui.repaint();
		}

	}
	
	/**
	*a method for checking whether the end of game has been reached
	*@return whether it is the end(some player have no cards in hand)
	*/
	public boolean endOfGame() {
		for (int i = 0 ; i<4;i++) {
			if(this.playerList.get(i).getCardsInHand().isEmpty()) {//||playerList.get(0).getCardsInHand().size()==0) {
				return true;
			}
		}
		return false;
	}
	
	
	/**
	* a method for starting a Big Two card game. It should (i)
	* create a Big Two card game, (ii) create and shuffle a deck of cards, and (iii) start the
	* game with the deck of cards
	* @param args is used for running tester function
	*/
	public static void main(String[] args) {
		BigTwo game = new BigTwo();
		BigTwoDeck deck = new BigTwoDeck();
		deck.shuffle(); 
		game.start(deck);
	}
	
	
	/**
	* a method for returning a valid hand from the specified list of cards of the player
	* @param player is the player that want to compose a hand
	* @param cards is the list of cards that is going to be composed.
	* @return a certain type of Hand, if no certain type of hand can be composed it returns null
	*/
	public static Hand composeHand(CardGamePlayer player, CardList cards) {
		//Hand hand = new Hand(player,cards);
		Single single = new Single(player,cards); 
		Pair pair = new Pair(player,cards); 
		Triple triple = new Triple(player,cards); 
		Straight straight = new Straight(player,cards); 
		Flush flush = new Flush(player,cards); 
		FullHouse fullhouse = new FullHouse(player,cards); 
		Quad quad = new Quad(player,cards); 
		StraightFlush straightflush = new StraightFlush(player,cards); 
		if(straightflush.isValid()){
			return straightflush;
		}
		else if(quad.isValid())
		{
			return quad; 
		}
		
		else if(fullhouse.isValid())
		{
			return fullhouse; 
		}
		
		else if(flush.isValid())
		{
			return flush; 
		}
		
		else if(straight.isValid())
		{	
			return straight; 
		}
		
		else if(triple.isValid())
		{
			return triple; 
		}
		
		else if(pair.isValid())
		{
			return pair; 
		}
		
		else if(single.isValid())
		{
			return single; 
		}
		return null;
	}
	
	

}